package com.tonyodev.fetch2core;

import ohos.utils.Parcel;
import ohos.utils.Sequenceable;
import ohos.utils.zson.ZSONObject;

import org.jetbrains.annotations.NotNull;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Class that holds custom key/value pair data for a request and download.
 * Use the get methods to get the custom data by type.
 * Call the toMutableExtras() method to get a new mutable/changeable instance of
 * this class that contains the instance data/extras.
 * <p>
 * Use the mutable version of this class to update information on a request/download
 * on a fetch instance with the matching namespace for the request/download.
 *
 * @see "com.tonyodev.fetch2.Fetch.replaceExtras(id, extras, func, func) method".
 * @since 2021-05-20
 */
public class Extras implements Sequenceable, Serializable {
    private static Extras emptyExtras = new Extras(new HashMap<>());
    @NotNull
    protected Map data;

    /**
     * 构造函数
     *
     * @param data map
     */
    public Extras(Map data) {
        this.data = data;
    }

    /**
     * 构造函数
     */
    public Extras() {
    }

    /**
     * Retrieve a string value for passed in matching key.
     *
     * @param key custom data key
     * @param defaultValue default value if value for the key does not exist.
     * @return value for the key if it exist otherwise the default value passed
     * to the method is returned.
     */
    public final String getString(@NotNull String key, @NotNull String defaultValue) {
        String value = (String) this.data.get(key);
        if (value == null) {
            value = defaultValue;
        }
        return value;
    }

    /**
     * Retrieve a int value for passed in matching key.
     *
     * @param key custom data key
     * @param defaultValue default value if value for the key does not exist.
     * @return value for the key if it exist otherwise the default value passed
     * to the method is returned.
     */
    public final int getInt(@NotNull String key, int defaultValue) {
        String value = (String) data.get(key);
        int cacheValue;
        if (value != null) {
            try {
                cacheValue = Integer.parseInt(value);
            } catch (NumberFormatException e) {
                cacheValue = defaultValue;
            }
        } else {
            cacheValue = defaultValue;
        }
        return cacheValue;
    }

    /**
     * Retrieve a long value for passed in matching key.
     *
     * @param key custom data key
     * @param defaultValue default value if value for the key does not exist.
     * @return value for the key if it exist otherwise the default value passed
     * to the method is returned.
     */
    public final long getLong(@NotNull String key, long defaultValue) {
        String value = (String) data.get(key);
        long cacheValue;
        if (value != null) {
            try {
                cacheValue = Long.parseLong(value);
            } catch (NumberFormatException e) {
                cacheValue = defaultValue;
            }
        } else {
            cacheValue = defaultValue;
        }
        return cacheValue;
    }

    /**
     * Retrieve a boolean value for passed in matching key.
     *
     * @param key custom data key
     * @param defaultValue default value if value for the key does not exist.
     * @return value for the key if it exist otherwise the default value passed
     * to the method is returned.
     */
    public final boolean getBoolean(@NotNull String key, boolean defaultValue) {
        String value = (String) data.get(key);
        boolean cacheValue;
        if (value != null) {
            cacheValue = Boolean.parseBoolean(value);
            if (!String.valueOf(cacheValue).equals(value)) {
                cacheValue = defaultValue;
            }
        } else {
            cacheValue = defaultValue;
        }
        return cacheValue;
    }

    /**
     * Retrieve a double value for passed in matching key.
     *
     * @param key custom data key
     * @param defaultValue default value if value for the key does not exist.
     * @return value for the key if it exist otherwise the default value passed
     * to the method is returned.
     */
    public final double getDouble(@NotNull String key, double defaultValue) {
        String value = (String) data.get(key);
        double cacheValue;
        if (value != null) {
            try {
                cacheValue = Double.parseDouble(value);
            } catch (NumberFormatException e) {
                cacheValue = defaultValue;
            }
        } else {
            cacheValue = defaultValue;
        }
        return cacheValue;
    }

    /**
     * Retrieve a float value for passed in matching key.
     *
     * @param key custom data key
     * @param defaultValue default value if value for the key does not exist.
     * @return value for the key if it exist otherwise the default value passed
     * to the method is returned.
     */
    public final float getFloat(@NotNull String key, float defaultValue) {
        String value = (String) data.get(key);
        float cacheValue;
        if (value != null) {
            try {
                cacheValue = Float.parseFloat(value);
            } catch (NumberFormatException e) {
                cacheValue = defaultValue;
            }
        } else {
            cacheValue = defaultValue;
        }
        return cacheValue;
    }

    /**
     * Create a new mutable instance of this class with the associated custom value data
     * attached.
     *
     * @return new mutable instance.
     */
    @NotNull
    public final MutableExtras toMutableExtras() {
        return new MutableExtras(new LinkedHashMap<>(this.data));
    }

    /**
     * Converts the custom value extras stored in this extra class as a JSON string.
     *
     * @return JSON string
     */
    @NotNull
    public final String toJSONString() {
        String strJson;
        if (this.isEmpty()) {
            strJson = "{}";
        } else {
            strJson = (new ZSONObject(this.getMap())).toString();
        }
        return strJson;
    }

    /**
     * Creates a new JSONObject containing the associated custom data.
     *
     * @return JSON string
     */
    @NotNull
    public final ZSONObject toJSONObject() {
        return this.isEmpty() ? new ZSONObject() : new ZSONObject(this.getMap());
    }

    /**
     * Get a copy of this instance with the associated custom data.
     *
     * @return new extras instance
     */
    @NotNull
    public Extras copy() {
        return new Extras(getMap());
    }

    /**
     * returns true if the extras object does not contain key value pairs. Otherwise false
     *
     * @return returns true if the extras object does not contain key value pairs. Otherwise false
     */
    public final boolean isEmpty() {
        return this.data.isEmpty();
    }

    /**
     * returns true if the extras object contains key value pairs. Otherwise false
     *
     * @return returns true if the extras object contains key value pairs. Otherwise false
     */
    public final boolean isNotEmpty() {
        return !data.isEmpty();
    }

    /**
     * Count of key/value pairs contained in this extras object.
     *
     * @return size
     */
    public final int getSize() {
        return this.data.size();
    }

    /**
     * The backing map that holds the custom key value pairs data.
     *
     * @return new map
     */
    @NotNull
    public final Map getMap() {
        if (data == null || data.size() == 0) {
            return new HashMap();
        }
        if (data.size() == 1) {
            Map.Entry entry = (Map.Entry) data.entrySet().iterator().next();
            return Collections.singletonMap(entry.getKey(), entry.getValue());
        } else {
            return new LinkedHashMap(this.data);
        }
    }

    @NotNull
    protected final Map getData() {
        return this.data;
    }

    /**
     * to stirng
     *
     * @return json string
     */
    @Override
    public String toString() {
        return toJSONString();
    }

    /**
     * 获取Extras
     *
     * @return emptyExtras
     */
    @NotNull
    public static final Extras getEmptyExtras() {
        return emptyExtras;
    }

    /**
     * 序列化
     *
     * @param parcel parcel
     * @return true
     */
    @Override
    public boolean marshalling(Parcel parcel) {
        parcel.writeSerializable(new HashMap<>(this.data));
        return true;
    }

    /**
     * 对比
     *
     * @param o obj
     * @return equals
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Extras extras = (Extras) o;
        return data.equals(extras.data);
    }

    /**
     * hash
     *
     * @return hashcode
     */
    @Override
    public int hashCode() {
        return data.hashCode();
    }

    /**
     * 反序列化
     *
     * @param parcel parcel
     * @return true
     */
    @Override
    public boolean unmarshalling(Parcel parcel) {
        this.data = parcel.readSerializable(HashMap.class);
        return true;
    }

    /**
     * createFromParcel
     */
    public static final Sequenceable.Producer
        PRODUCER = new Sequenceable.Producer() {

        /**
         * 通过反序列化创建对象
         *
         * @param in read
         * @return Extras
         */
        public Extras createFromParcel(Parcel in) {
            // Initialize an instance first, then do customized unmarshlling.
            Extras instance = new Extras();
            instance.unmarshalling(in);
            return instance;
        }
    };
}
